import json
import re
import ahocorasick as ah
import jieba
import jieba.posseg as pseg


class DimensionDetection:
    def __init__(self, data_source_path):
        data_dict = json.load(open(data_source_path, 'r', encoding='utf-8'))
        self.data_source = data_dict[1]
        # self.headers = self.data_source['header']
        self.alias2id = {}
        self.id2alias = {}
        self.alias2field = {}
        self.alias2associated = {}
        self.alias2header = {}
        self.id2members = {}
        self.objname2alias = {}
        for header in self.data_source['header']:
            self.alias2id[header['alias']] = header['id']
            self.id2alias[header['id']] = header['alias']
            self.alias2field[header['alias']] = header['fieldType']
            self.alias2associated[header['alias']] = header['associatedFields']
            self.alias2header[header['alias']] = header

            if self.objname2alias.get(header['objectName']) is None:
                self.objname2alias[header['objectName']] = []
            self.objname2alias[header['objectName']].append(header['alias'])

        for row in self.data_source['rows']:
            self.id2members[row['id']] = row['members']

        self.aca_dim = ah.Automaton()
        self.aca_mem = ah.Automaton()
        self.dim_tokens = {}

        # 添加维度名称
        for dim in list(self.alias2id.keys()):
            self.aca_dim.add_word(dim, dim)
            tokens = jieba.lcut(dim)
            for t in tokens:
                if self.dim_tokens.get(t) is None:
                    self.dim_tokens[t] = []
                self.dim_tokens[t].append(dim)
        # 添加维度成员
        for id, members in self.id2members.items():
            alias = self.id2alias[id]
            for member in members:
                self.aca_mem.add_word(member, member)

        self.aca_dim.make_automaton()
        self.aca_mem.make_automaton()

    def dim_len(self):
        return len(self.data_source['header'])

    def show_all_dim(self):
        return list(self.alias2id.keys())

    def show_dim_members(self, dim):
        return self.id2members[self.alias2id[dim]]

    def partial_match_member(self, word):
        """
        like查询维度成员，如广州，将返回广州分部，广州市，广州公司等维度成员的所属维度
        :param word: (String)
        :return: List[]
        """
        match_dim = []
        for id, members in self.id2members.items():
            alias = self.id2alias[id]
            for member in members:
                m = re.search(word, member)
                if m is not None and alias not in match_dim:
                    match_dim.append(alias)
        return match_dim

    def full_match_member(self, word):
        """
        完全匹配维度成员，如广州市，将返回广州市的所属维度
        :param word: String
        :return: List[]
        """
        match_dim = []
        for id, members in self.id2members.items():
            alias = self.id2alias[id]
            for member in members:
                if word == member and alias not in match_dim:
                    match_dim.append(alias)
        return match_dim

    def detect_dim(self, query):
        result_dim = []
        for dim in self.aca_dim.iter_long(query):
            result_dim.append(dim[1])
        return result_dim

    def detect_member(self, query):
        """
        在所有维度成员中找到查询query中出现的维度成员，返回完整匹配的维度成员
        :param query: String
        :return: List[]
        """
        result_mem = []
        for item in self.aca_mem.iter_long(query):
            result_mem.append(item[1])
        return result_mem

    def cut_query(self, query, word):
        seg_list = query.split(word)
        seg_query = "".join(seg_list)
        return seg_query

    def add_dim_to_output(self, match_dim, dim_dict):
        for dim in dim_dict.values():
            if dim == '不进行选择':
                continue
            match_dim['DIMENSION'].append(dim)
        match_dim = {k: sorted(set(v), key=v.index) for k, v in match_dim.items()}
        return match_dim

    def output_formatter(self, query, match_dim):
        format_output = {
            'question': query,
            'sel': [],
            'like': []
        }

        for t, dim_list in match_dim.items():
            for dim in dim_list:
                id = self.alias2id[dim]
                if self.id2members.get(id) is None:
                    mem_amo = 0
                else:
                    mem_amo = len(self.id2members[id])
                dim_json = {
                    'member_amount': mem_amo,
                    'name': dim,
                    'timeLevel': self.alias2header[dim]['timeLevel'] if self.alias2header[dim].get(
                        'timeLevel') is not None else "",
                    'id': self.alias2id[dim],
                    'type': self.alias2header[dim]['type'],
                }
                format_output['sel'].append(dim_json)
        return format_output

    def meta_output_formatter(self, query, match_meta):
        format_output = {
            'question': query,
            'meta': match_meta
        }
        return format_output

    def choice_formatter(self, choices):
        choice_formatter = []
        for k, v in choices.items():
            choice_json = {
                'ambiguity_dim': k,
                'candidates': v
            }
            choice_formatter.append(choice_json)
        return choice_formatter

    def like_formatter(self, output, dim_dict):
        like_list = []
        for k, v in dim_dict.items():
            if v == '不进行选择':
                continue
            # 跳过非模糊成员
            if len(self.detect_member(k)) > 0:
                continue
            like_json = {'id': self.alias2id[v], 'literal': k}
            like_list.append(like_json)
        output['like'] += like_list
        return output

    def add_dim_pos(self, dim_pos, dim_dict, query):
        for k, v in dim_dict.items():
            if v == '不进行选择':
                continue
            span = re.search(k, query).span()
            dim_pos.append((v, span))
        return dim_pos

    def detect(self, query):
        origin_query = query

        # 元数据查询
        objlist = []
        for objname in self.objname2alias.keys():
            if re.search(objname, query) is not None:
                objlist.append(objname)

        # 如果存在元数据查询，则返回元数据以及对应维度
        if len(objlist) != 0:
            match_meta = {}
            for obj in objlist:
                match_meta[obj] = self.objname2alias[obj]
            return (match_meta)

        # 完整列名匹配
        match_dim = {'DIMENSION': [], 'MEASURE': []}
        choices = {}
        dim_pos = []

        detected_dim = self.detect_dim(query)
        for dim in detected_dim:
            field_type = self.alias2field[dim]
            if match_dim.get(field_type) is None:
                match_dim[field_type] = []
            match_dim[field_type].append(dim)
            query = self.cut_query(query, dim)

            span = re.search(dim, origin_query).span()
            dim_pos.append((dim, span))

        # 时间维度匹配
        time_dim = ['年', '季', '月', '日', '开始日期', '截止日期']
        pattern1 = (r'\d{4}[-,/]\d{1,2}', ['年', '月'])
        pattern2 = (r'\d{2,4}[-,/]\d{1,2}[-,/]\d{1,2}', ['年', '月', '日'])
        if re.search(pattern2[0], query) is not None:
            for dim in pattern2[1]:
                match_dim[self.alias2field[dim]].append(dim)
                dim_pos.append((dim, (0, 0)))
        elif re.search(pattern1[0], query) is not None:
            for dim in pattern1[1]:
                match_dim[self.alias2field[dim]].append(dim)
                dim_pos.append((dim, (0, 0)))
        # if re.search('截至|截止|截止至', query) is not None:
        #     for dim in ['开始日期', '截止日期']:
        #         match_dim[self.alias2field[dim]].append(dim)
        #         dim_pos.append((dim, (0, 0)))
        if re.search('今天|昨天|前天|后天|明天|第.天|每天', query) is not None:
            dim = '日'
            match_dim[self.alias2field[dim]].append(dim)
            dim_pos.append((dim, (0, 0)))
        if re.search('q1|q2|q3|q4|Q1|Q2|Q3|Q4', query) is not None:
            dim = '季'
            match_dim[self.alias2field[dim]].append(dim)
            dim_pos.append((dim, (0, 0)))
        
        # 如果没有识别到时间维度，默认是今年（目前任何情况下都要加年）
        # has_time = False
        # for time in time_dim:
        #     if time in match_dim['DIMENSION']:
        #         has_time = True
        # if has_time == False:
        #     match_dim['DIMENSION'].append('年')
        #     dim_pos.append(('年', (0, 0)))

        if '年' not in match_dim['DIMENSION']:
            match_dim['DIMENSION'].append('年')
            dim_pos.append(('年', (0, 0)))

        # 维度成员匹配
        # 匹配得到的是完整的维度成员名称
        query_mem_list = self.detect_member(query)
        # 如果匹配到完整的维度成员
        if len(query_mem_list) > 0:
            for mem in query_mem_list:
                query = self.cut_query(query, mem)
                full_match = self.full_match_member(mem)
                fliter_match = []
                # 如果维度成员在多个维度出现
                if len(full_match) != 1:
                    for t in full_match:
                        # 如果query存在measure
                        if len(match_dim['MEASURE']) > 0:
                            query_measure_list = match_dim['MEASURE']
                            # 广州分部的合同金额和确认收入_进度法 --> 销售分部
                            associated = [True if self.alias2id[t] in self.alias2associated[measure] else False
                                          for measure in query_measure_list]
                            if False not in associated:
                                fliter_match.append(t)
                        else:
                            fliter_match.append(t)

                    if len(fliter_match) == 1:
                        match_dim['DIMENSION'].append(fliter_match[0])
                        span = re.search(mem, origin_query).span()
                        dim_pos.append((fliter_match[0], span))

                    elif len(fliter_match) > 1:
                        # 选择的数量限制为五个
                        to_sorted = []
                        for t in fliter_match:
                            associate_len = len(self.alias2associated[t])
                            to_sorted.append({'dimension': t, 'associate': associate_len})
                        sorted(to_sorted, key=lambda t: t['associate'], reverse=True)
                        top4_match = [d['dimension'] for d in to_sorted[:4]]
                        choices[mem] = top4_match

                elif len(full_match) == 1:
                    # 如果没有歧义，但是维度与度量无关，如交付广州分部（二级部门）与签单商机个数，如何处理？
                    fliter_match = full_match
                    match_dim['DIMENSION'].append(fliter_match[0])
                    span = re.search(mem, origin_query).span()
                    dim_pos.append((fliter_match[0], span))

        # 维度成员like查询
        like_dict = {}
        jieba.enable_paddle()
        proper_noun_list = ['LOC', 'PER', 'ORG', 'ns', 'nr', 'nt']
        # proper_noun_list = ['LOC', 'PER', 'ORG']
        proper_dim_map = {
            'LOC': ['销售分部', '二级部门', '客户所在城市', '客户所在省', '渠道所在城市', '渠道所在省'],
            'ns': ['销售分部', '二级部门', '客户所在城市', '客户所在省', '渠道所在城市', '渠道所在省'],
            'PER': ['员工姓名', '销售姓名'],
            'nr': ['员工姓名', '销售姓名'],
            'ORG': ['商机名称', '渠道名称'],
            'nt': ['商机名称', '渠道名称']
        }

        # 去掉数字等无关字符
        query = re.sub('[0-9]+', '', query)

        words = pseg.cut(query, use_paddle=True)
        words = {word: flag for word, flag in words}
        for noun in proper_noun_list:
            members = [k for k, v in words.items() if v == noun]
            for mem in members:
                part_match = self.partial_match_member(mem)
                query = self.cut_query(query, mem)
                fliter_match = []
                if len(part_match) != 1:
                    if len(part_match) == 0:
                        part_match = proper_dim_map[noun]
                    for t in part_match:
                        if len(match_dim['MEASURE']) > 0:
                            query_measure_list = match_dim['MEASURE']
                            # 广州分部的合同金额和确认收入_进度法 --> 销售分部
                            associated = [True if self.alias2id[t] in self.alias2associated[measure] else False
                                          for measure in query_measure_list]
                            if False not in associated:
                                fliter_match.append(t)
                        else:
                            fliter_match.append(t)

                    if len(fliter_match) == 1:
                        match_dim['DIMENSION'].append(fliter_match[0])
                        like_dict[mem] = fliter_match[0]
                        span = re.search(mem, origin_query).span()
                        dim_pos.append((fliter_match[0], span))

                    elif len(fliter_match) > 1:
                        # 选择的数量限制为五个
                        to_sorted = []
                        for t in fliter_match:
                            associate_len = len(self.alias2associated[t])
                            to_sorted.append({'dimension': t, 'associate': associate_len})
                        sorted(to_sorted, key=lambda t: t['associate'], reverse=True)
                        top4_match = [d['dimension'] for d in to_sorted[:4]]
                        choices[mem] = top4_match

                elif len(part_match) == 1:
                    fliter_match = part_match
                    match_dim['DIMENSION'].append(fliter_match[0])
                    like_dict[mem] = fliter_match[0]
                    span = re.search(mem, origin_query).span()
                    dim_pos.append((fliter_match[0], span))


        # 冗余动词去除
        words = pseg.cut(query, use_paddle=True)
        words = {word: flag for word, flag in words}
        vs = [k for k, v in words.items() if v in ['v', 'vd', 'vn']]
        for v in vs:
            query = self.cut_query(query, v)

        # 列名like
        words = jieba.lcut(query)
        for word in words:
            # 跳过单字的like
            if len(word) == 1:
                continue
            if self.dim_tokens.get(word) is not None:
                dim_list = self.dim_tokens.get(word)
                if len(dim_list) > 1:
                    choices[word] = dim_list
                elif len(dim_list) == 1:
                    field_type = self.alias2field[dim_list[0]]
                    match_dim[field_type] += dim_list
                    span = re.search(word, origin_query).span()
                    dim_pos.append((dim_list[0], span))

        # match_dim = sorted(set(match_dim), key=match_dim.index)
        match_dim = {k: sorted(set(v), key=v.index) for k, v in match_dim.items()}

        # 选择
        # if len(choices.keys()) > 0:
        #     print("有%d个维度需要进行选择，请选择数字编号！" % len(choices.keys()))
        #     for like, dim_list in choices.items():
        #         print("对于查询中的[%s]，你的选择是：" % like)
        #         for i, dim in enumerate(dim_list):
        #             print("%d、%s" % (i+1, dim))
        #         c = input()
        #         c = int(c)
        #         assert c in list(range(1, len(dim_list)+1)), "The choice number out of size!"
        #         choice_dim = dim_list[c-1]
        #         match_dim['DIMENSION'].append(choice_dim)

        # 格式化
        # format_output = self.formatter(origin_query, match_dim)
        for k, v in choices.items():
            v.append('不进行选择')

        detect_result = (match_dim, choices, like_dict, dim_pos)
        return detect_result


if __name__ == '__main__':
    dim_detect = DimensionDetection('data/data_dict.json')
    query = "今年广州分部的合同金额"
    output, choices, like_dict, dim_pos = dim_detect.detect(query)
    output = dim_detect.output_formatter(query, output)
    print(output)
    print(choices)
    print(like_dict)
    print(dim_pos)
